/* Sentinel - IRC Statistical and Operator Services
** splitserv.c - Split service interface
**
** Copyright W. Campbell and others.  See README for more details
** Some code Copyright: Jonathan George, Kai Seidler, ircd-hybrid Team,
**                      IRCnet IRCD developers.
**
** $Id: splitserv.c 7 2010-02-01 19:17:42Z dubkat $
*/

#include "stats.h"
#include "struct.h"
#include "proto.h"
#include "text.h"
#include "match.h"
#include "s_string.h"
#include "slog.h"

#ifdef SPLITSERV

struct SplTable
{
  char* name;
  long counter;
  int channel;  /* 1 if it's allowed in a channel */
  int (*handler)();
};

static char errstr[BUFSIZE];

static int sp_map(struct User *, char *, int, char **);
static int sp_split(struct User *, char *, int, char **);
static int sp_dump(struct User *, char *, int, char **);
static int sp_add(struct User *, char *, int, char **);
static int sp_forget(struct User *, char *, int, char **);
static int sp_update(struct User *, char *, int, char **);
static int sp_help(struct User *, char *, int, char **);
static int sp_chelp(struct User *, char *, int, char **);
static int sp_cping(struct User *, char *, int, char **);
static int sp_cversion(struct User *, char *, int, char **);
static int sp_triggers(struct User *, char *, int, char **);

static struct SplTable sp_table[] =
  {
    { TRIGGER_SPLIT, 0, 1, sp_split },
    { TRIGGER_MAP, 0, 1, sp_map },
    { "MAP", 0, 0, sp_map },
    { "SPLIT", 0, 0, sp_split },
    { TRIGGER_DUMP, 0, 1, sp_dump },
    { "DUMP", 0, 0, sp_dump },
    { "FORGET", 0, 0, sp_forget },
    { "ADD", 0, 0, sp_add },
    { "UPDATE", 0, 0, sp_update },
    { "HELP", 0, 0, sp_help },
    { TRIGGER_HELP, 0, 1, sp_chelp },
    { TRIGGER_TRIGGERS, 0, 1, sp_triggers },
    { "TRIGGERS", 0, 0, sp_triggers },
    { "\001PING", 0, 1, sp_cping },
    { "\001VERSION\001", 0, 1, sp_cversion },
    { (char *)0, 0, 0, (int (*)()) 0 }
  };

static struct HelpTable help_table[] =
  {
    { TRIGGER_MAP, "help/splitserv/map", 0 },
    { "MAP", "help/splitserv/map", 0 },
    { TRIGGER_SPLIT, "help/splitserv/split", 0 },
    { "SPLIT", "help/splitserv/split", 0 },
    { TRIGGER_DUMP, "help/splitserv/dump", 0 },
    { "DUMP", "help/splitserv/dump", 0 },
    { "FORGET", "help/splitserv/forget", 1 },
    { "ADD", "help/splitserv/add", 1 },
    { "UPDATE", "help/splitserv/update", 1 },
    { (char *)0, (char *)0, 0 }
  };

void splitserv(struct User *u, char *target, char *param)
{
  char *parv[MAXPARA];
  int parc = 0;
  int i;
  int status;
  static char buf[BUFSIZE];

  /* Clear the parv */
  for (i = 0; i < MAXPARA; i++)
    parv[i] = NULL;

  /* Clear the error string */
  errstr[0] = '\0';

  strlcpy(buf, param, BUFSIZE);

  if (param != NULL)
    parc = msgtoken(param, parv);

  netinfo.requests++;

  if (parc == 0)
    return ;

  /* This will generate some extraneous log file entries for
  ** messages such as :) and .
  */
  if (!irccasecmp(target, sp.join_chan) &&
      (isalpha((int) parv[0][0]) || isdigit((int) parv[0][0])))
    return ;

  if (parv[0][0] != '\001')
    slog(DEFAULT_LOG, L_INFO, "%s received message from %s to %s: %s",
         SplitServ.nick, u->nick, target, buf);

  if (IsSPOperOnly() && !IsOper(u))
  {
    if (parv[0][0] != '\001')
    {
      notice(u->nick, SplitServ.nick,
              "This service is only available to IRCops.");
      return ;
    }
  }

  /* All the preliminary junk is done, now we have to go through
  ** the tables and call the handler for that command
  */
  for (i = 0; sp_table[i].handler; i++)
  {
    if (!strcasecmp(parv[0], sp_table[i].name))
    {
      if (is_channel(target) && !sp_table[i].channel)
        return ;
      status = sp_table[i].handler(u, target, parc, parv);
      if (status == 1)
        slog(SPLIT_LOG, L_NOTICE, "Error with splitserv command %s: %s", parv[0],
             (errstr) ? errstr : "No error message");
      /* Matched a value, done, then return */
      return ;
    }
  }
  /* If we get here, we did not get a valid command...
  ** Make sure not to say anything to the user if it was a 
  ** CTCP that was not matched
  */
  if (*parv[0] == '\001')
    return ;
  /* Don't answer invalid commands if it was a message to a channel */
  if (is_channel(target))
    return ;
  notice(u->nick, SplitServ.nick, "Invalid command: %s", parv[0]);
  return ;
}

/* In all of these functions, parv[0] contains the command, followed
** by the parameters
*/

static int sp_chelp(struct User *u, char *target, int parc, char *parv[])
{
  if (irccasecmp(sp.join_chan, target))
  {
    /* This happens to not be a channel message */
    return sp_help(u, target, parc, parv);
  }
  send_help_file(sp.join_chan, SplitServ.nick,
                 "help/splitserv/pindex", "pindex");
  return 0;
}

static int sp_help(struct User *u, char *target, int parc, char *parv[])
{
  int i;

  if (parc < 2)
  {
    send_help_file(u->nick, SplitServ.nick, "help/splitserv/index", "index");
    return 0;
  }

  for (i = 0; help_table[i].filename; i++)
  {
    if (!strcasecmp(parv[1], help_table[i].name))
    {
      if (help_table[i].myuonly)
      {
        if (u->myuser)
          send_help_file(u->nick, SplitServ.nick, help_table[i].filename,
                         help_table[i].name);
        else
          notice(u->nick, SplitServ.nick, "Permission Denied.");
      }
      else
      {
        send_help_file(u->nick, SplitServ.nick, help_table[i].filename,
                       help_table[i].name);
      }

      return 0;
    }
  }

  notice(u->nick, SplitServ.nick, "Unknown Help Topic: %s", parv[1]);
  snprintf(errstr, BUFSIZE, "Unknown HELP topic: %s", parv[1]);
  return 1;
}

static int sp_map(struct User *u, char *target, int parc, char *parv[])
{
  struct SpServer *root;

  if (sp.map_setting == SO_DISABLED)
  {
    notice(u->nick, SplitServ.nick, "The map function is disabled.");
    return 0;
  }

  if (sp.map_setting == SO_OPERONLY)
  {
    if (!IsOper(u))
    {
      notice(u->nick, SplitServ.nick,
              "The map function is only available to opers.");
      return 0;
    }
  }

  if (parv[1] == NULL)
  {
    root = map_head;
  }
  else
  {
    char *dest = is_channel(target) ? target : u->nick;

    root = SpFindServer(parv[1]);
    if (root == NULL)
    {
      notice(dest, SplitServ.nick, "No such server %s", parv[1]);
      return 0;
    }
    notice(dest, SplitServ.nick, "NOTICE:  Starting from server %s", parv[1]);
  }

  if (is_channel(target))
    PrintMap(target, root, 0, NULL);
  else
    PrintMap(u->nick, root, 0, NULL);
  return 0;
}

static int sp_split(struct User *u, char *target, int parc, char *parv[])
{
  if (sp.split_setting == SO_DISABLED)
  {
    notice(u->nick, SplitServ.nick, "The split function is disabled.");
    return 0;
  }

  if (sp.split_setting == SO_OPERONLY)
  {
    if (!IsOper(u))
    {
      notice(u->nick, SplitServ.nick,
              "The split function is only available to opers.");
      return 0;
    }
  }

  if (is_channel(target))
    PrintSplit(target);
  else
    PrintSplit(u->nick);
  return 0;
}

static int sp_dump(struct User *u, char *target, int parc, char *parv[])
{
  if (sp.dump_setting == SO_DISABLED)
  {
    notice(u->nick, SplitServ.nick, "The dump function is disabled.");
    return 0;
  }

  if (sp.dump_setting == SO_OPERONLY)
  {
    if (!IsOper(u))
    {
      notice(u->nick, SplitServ.nick,
              "The dump function is only available to opers.");
      return 0;
    }
  }

  if (is_channel(target))
    PrintDump(target);
  else
    PrintDump(u->nick);
  return 0;
}

static int sp_forget(struct User *u, char *target, int parc, char *parv[])
{
  struct SpServer *s;
  dlink_node *dl;

  if ((u->myuser != NULL) && IsSetOperSplit(u->myuser))
  {
    if (!irccasecmp(target, SplitServ.nick))
    {
      if (parc < 2)
      {
        notice(u->nick, SplitServ.nick, "Cannot forget a null server");
        return 0;
      }

      DLINK_FOREACH(dl, split_table.head)
      {
        s = (struct SpServer *) dl->data;
        if (!irccasecmp(s->name, parv[1]))
        {
          notice(u->nick, SplitServ.nick, "Removed from the Split Table",
                  s->name);
          dlink_delete(dl, &split_table);
          if (IsSPReport())
            notice(sp.join_chan, SplitServ.nick, "FORGOT SPLIT: %s", s->name);
          slog(SPLIT_LOG, L_NOTICE, "Forgot split %s by %s", s->name,
               u->nick);
          free(s);
          memcounts.spserver--;
          dlink_free(dl);
          return 0;
        }
      }

      notice(u->nick, SplitServ.nick, "%s NOT found in the Split Table",
             parv[1]);
    }
  }
  return 0;
}

static int sp_add(struct User *u, char *target, int parc, char *parv[])
{
  struct SpServer *s = NULL;

  if ((u->myuser != NULL) && IsSetOperSplit(u->myuser))
  {
    if (!irccasecmp(target, SplitServ.nick))
    {
      if (parc < 2)
      {
        notice(u->nick, SplitServ.nick, "Can't add a split for a null server");
        return 0;
      }

      s = SpFindServer(parv[1]);
      if (s != NULL)
      {
        notice(u->nick, SplitServ.nick,
                "Can't add a split for an existing server");
        return 0;
      }
      s = SpFindSplit(parv[1]);
      if (s != NULL)
      {
        notice(u->nick, SplitServ.nick,
                "Can't add a split for an existing split");
        return 0;
      }
      SpNewSplit(parv[1]);
      if (IsSPReport())
        notice(sp.join_chan, SplitServ.nick, "ADDED SPLIT: %s", parv[1]);
      slog(SPLIT_LOG, L_NOTICE, "Added split %s by %s", parv[1], u->nick);
    }
  }
  return 0;
}

static int sp_update(struct User *u, char *target, int parc, char *parv[])
{
  if ((u->myuser != NULL) && IsSetOperSplit(u->myuser))
  {
    if (!irccasecmp(target, SplitServ.nick))
    {
      if (IsSPEnableText())
      {
        dump_map();
        notice(u->nick, SplitServ.nick, "MAP file updated");
        return 0;
      }
      notice(u->nick, SplitServ.nick, "MAP Text Dump Disabled");
    }
  }
  return 0;
}

static int sp_cversion(struct User *u, char *target, int parc, char *parv[])
{
  sts(":%s NOTICE %s :\001VERSION %s %s -> %s\001",
      SplitServ.nick, u->nick, stats_version, VersionFlags(), stats_date);
  slog(SPLIT_LOG, L_NOTICE, "CTCP VERSION from %s!%s@%s", u->nick, u->username, u->hostname);

  return 0;
}

static int sp_cping(struct User *u, char *target, int parc, char *parv[])
{
  char command[BUFSIZE];

  ExpandParv(command, BUFSIZE, 1, parc, parv);
  if (command[0] == '\0')
    return 1;
  sts(":%s NOTICE %s :\001PING %s", SplitServ.nick, u->nick, command);
  slog(DEFAULT_LOG, L_INFO, "CTCP PING from %s!%s@%s", u->nick, u->username,
       u->hostname);

  return 0;
}

static int sp_triggers(struct User *u, char *target, int parc, char *parv[])
{
  if (is_channel(target))
    notice(target, SplitServ.nick, "Public Triggers: %s %s %s",
            TRIGGER_SPLIT, TRIGGER_MAP, TRIGGER_DUMP);
  else
    notice(u->nick, SplitServ.nick, "Public Triggers: %s %s %s",
            TRIGGER_SPLIT, TRIGGER_MAP, TRIGGER_DUMP);
  return 0;
}

#endif
